﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using NClass.Core;
using NClass.TestHelper;
using NClass.Translations;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;

namespace NClass.Test.Core
{
	[TestClass]
	public class ModelTest
	{
		private class LoadRelationshipsDataSet
		{
			internal LoadRelationshipsDataSet(string xml, Func<Model, string> outputValidator, Func<Exception, bool> exceptionValidator)
			{
				if (!string.IsNullOrWhiteSpace(xml))
				{
					XmlNode = LoadElement(xml);
				}

				ValidateException = exceptionValidator;
				ValidateOutput = outputValidator;
			}

			private static XmlElement LoadElement(string xml)
			{
				var element = (new XmlDocument()).CreateElement("Node");
				element.InnerXml = xml;
				return element;
			}

			internal XmlNode XmlNode { get; private set; }
			internal Func<Model, string> ValidateOutput { get; private set; }
			internal Func<Exception, bool> ValidateException { get; private set; }
		}

		private const string _nodeSet1 = "<Relationships><Relationship type=\"\" first=\"\" second=\"\" /></Relationships>";
		private const string _nodeSet2 = "<Relations><Relation type=\"\" first=\"1\" second=\"\" /></Relations>";

		private const string _nodeSet3 = "<Relationships><Relationship type=\"\" first=\"-1\" second=\"\" /></Relationships>";
		private const string _nodeSet4 = "<Relationships><Relationship type=\"\" first=\"0\" second=\"-1\" /></Relationships>";
		private const string _nodeSet5 = "<Relationships><Relationship type=\"\" first=\"20\" second=\"0\" /></Relationships>";
		private const string _nodeSet6 = "<Relationships><Relationship type=\"\" first=\"0\" second=\"20\" /></Relationships>";

		private const string _nodeSet7 = "<Relationships><Relationship type=\"\" first=\"0\" second=\"1\" /></Relationships>";
		private const string _nodeSet8 = "<Relationships><Relationship type=\"Generalization\" first=\"0\" second=\"9\" /></Relationships>";
		private const string _nodeSet9 = "<Relationships><Relationship type=\"Generalization\" first=\"0\" second=\"0\" /></Relationships>";

		private const string _nodeSet10 = "<Relationships>" +
			"<Relationship type=\"Association\" first=\"0\" second=\"0\" />" +
			"<Relationship type=\"Generalization\" first=\"0\" second=\"1\" />" +
			"<Relationship type=\"Realization\" first=\"1\" second=\"3\" />" +
			"<Relationship type=\"Dependency\" first=\"2\" second=\"4\" />" +
			"<Relationship type=\"Nesting\" first=\"0\" second=\"2\" />" +
			"<Relationship type=\"Comment\" first=\"0\" second=\"12\" />" +
			"<Relationship type=\"CommentRelationship\" first=\"13\" second=\"2\" />" +
			"</Relationships>";

		/*private const string _nodeSet2 =
			"<Relations>" +
			"<Relation />" +
			"<Relation />" +
			"<Relation />" +
			"<Relation />" +
			"<Relation />" +
			"</Relations>";*/

		[TestMethod]
		[TestCategoryAttribute("Branch Path Coverage")]
		public void LoadRelationships()
		{
			var dataSets = new List<LoadRelationshipsDataSet>();

			dataSets.Add(new LoadRelationshipsDataSet(null, null, (exception) => { return exception is ArgumentNullException; }));
			dataSets.Add(new LoadRelationshipsDataSet(_nodeSet1, null, (exception) => { return exception is InvalidDataException && exception.Message == Strings.ErrorCorruptSaveFormat; }));
			dataSets.Add(new LoadRelationshipsDataSet(_nodeSet2, null, (exception) => { return exception is InvalidDataException && exception.Message == Strings.ErrorCorruptSaveFormat; }));

			dataSets.Add(new LoadRelationshipsDataSet(_nodeSet3, null, (exception) => { return exception is InvalidDataException && exception.Message == Strings.ErrorCorruptSaveFormat; }));
			dataSets.Add(new LoadRelationshipsDataSet(_nodeSet4, null, (exception) => { return exception is InvalidDataException && exception.Message == Strings.ErrorCorruptSaveFormat; }));
			dataSets.Add(new LoadRelationshipsDataSet(_nodeSet5, null, (exception) => { return exception is InvalidDataException && exception.Message == Strings.ErrorCorruptSaveFormat; }));
			dataSets.Add(new LoadRelationshipsDataSet(_nodeSet6, null, (exception) => { return exception is InvalidDataException && exception.Message == Strings.ErrorCorruptSaveFormat; }));

			dataSets.Add(new LoadRelationshipsDataSet(_nodeSet7, null, (exception) => { return exception is InvalidDataException && exception.Message == Strings.ErrorCorruptSaveFormat; }));
			dataSets.Add(new LoadRelationshipsDataSet(_nodeSet8, null, (exception) =>
			{
				return exception is InvalidDataException &&
					exception.Message == "Invalid relationship." &&
					exception.InnerException is ArgumentNullException;
			}));
			dataSets.Add(new LoadRelationshipsDataSet(_nodeSet9, null, (exception) =>
			{
				return exception is InvalidDataException &&
					exception.Message == "Invalid relationship." &&
					exception.InnerException is RelationshipException;
			}));

			dataSets.Add(new LoadRelationshipsDataSet(_nodeSet10, null, null));

			var methodInfo = (typeof(Model)).GetMethod("LoadRelationships",
				BindingFlags.NonPublic | BindingFlags.Instance);

			Language language;
			foreach (var dataSet in dataSets)
			{
				var model = Resolver.CreateModel(Resolver.Language.CSharp, out language);
				// 0 - 2
				model.AddClass();
				model.AddClass();
				model.AddClass();
				// 3 - 5
				model.AddInterface();
				model.AddInterface();
				model.AddInterface();
				// 6 - 8
				model.AddDelegate();
				model.AddDelegate();
				model.AddDelegate();
				// 9 - 11
				model.AddEnum();
				model.AddEnum();
				model.AddEnum();
				// 12 - 14
				model.AddComment();
				model.AddComment();
				model.AddComment();

				try
				{
					methodInfo.Invoke(model, new object[] { dataSet.XmlNode });

					if (dataSet.ValidateOutput != null)
					{
						var errorMessage = dataSet.ValidateOutput(model);

						if (!string.IsNullOrWhiteSpace(errorMessage))
							Assert.Fail(errorMessage);
					}
					else if (dataSet.ValidateException != null)
					{
						Assert.Fail("DataSet expected the input vector to cause an exception");
					}
				}
				catch (TargetInvocationException ex)
				{
					if (dataSet.ValidateException != null)
					{
						if (!dataSet.ValidateException(ex.InnerException))
						{
							Assert.Fail(ex.InnerException.Message);
						}
					}
					else
						Assert.Fail(ex.InnerException.Message);
				}
				catch (Exception ex)
				{
					Assert.Fail(ex.Message);
				}
			}
		}

		[TestMethod]
		[TestCategoryAttribute("Branch Path Coverage")]
		public void GetEntity()
		{
			// To cover all branches, all inputs must be given.
			// Since it is a switch the test set is fairly straight forward.
			var dataSet = new Dictionary<Type, IEnumerable<string>>()
			{
				{typeof(ClassType), new [] { "Class", "CSharpClass", "JavaClass"}},
				{typeof(StructureType), new [] { "Structure", "StructType"}},
				{typeof(InterfaceType), new [] { "Interface", "CSharpInterface", "JavaInterface"}},
				{typeof(EnumType), new [] { "Enum", "CSharpEnum", "JavaEnum"}},
				{typeof(DelegateType), new [] { "Delegate", "DelegateType"}},
				{typeof(Comment), new [] { "Comment", "Comment"}},
				{typeof(Exception), new [] { ""}}
			};

			var model = new Model(Resolver.GetLanguage(Resolver.Language.CSharp));

			object result = null;
			var errorMessage = string.Empty;
			foreach (var set in dataSet)
			{
				foreach (var inputVector in set.Value)
				{
					var vectorMessage = string.Format("Input Vector<{0},{1}>. ", set.Key.ToString(), inputVector);
					try
					{
						Assert.AreEqual(MethodValidator.Result.Succeeded, MethodValidator.CheckParameterValidation<Model, Type, IEntity>(model,
							"GetEntity", "ModelTest", "GetEntity", out errorMessage, out result,
							new List<object>() { inputVector }, set.Key,
							(expected, actual) =>
							{
								if (expected == null)
									return actual == null;
								if (actual == null)
									return false;

								return expected.IsAssignableFrom(actual.GetType());

							}), vectorMessage + errorMessage);
					}
					catch (Exception ex)
					{
						Assert.AreEqual(set.Key, typeof(Exception), vectorMessage + ex.InnerException.Message);
					}
				}
			}
		}
	}
}
